/*
* program name: servercls.cpp 此程序用于演示多进程的服务端
*/

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>


using namespace std;

class ctcpserver
{
private: 
	int m_listenfd;
	int m_clientfd;
	string m_clientip;
	unsigned short m_port;

public:
	ctcpserver():m_listenfd(-1),m_clientfd(-1) {}

	// 初始化用于监听的socket
	bool initserver(const unsigned short in_port)
	{
		// 第一步：创建服务端的socket。
		if( (m_listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) return false;
		
		m_port = in_port;

		// 第二步：把服务端用于通信的IP和端口绑定到socket上。
		struct sockaddr_in servaddr;
		memset(&servaddr, 0, sizeof(servaddr));
  		servaddr.sin_family = AF_INET;
  		servaddr.sin_port = htons(m_port);
  		servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
		
		 // 绑定服务端的IP和端口
  		if (bind(m_listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0)
  		{
			close(m_listenfd); m_listenfd = -1; return false;
  		}

		// 第三步：把socket设置为可连接状态
  		if (listen(m_listenfd, 5) != 0)
  		{
			close(m_listenfd); m_listenfd = -1; return false;
  		}
		
		return true;
	}

	// 受理客户端的连接（从已连接的客户端中取出一个客户端）
	// 如果没有已连接的客户端，accept()函数将阻塞等待。
	bool accept()
	{
		struct sockaddr_in caddr;
		socklen_t addrlen;
	
		if ((m_clientfd = ::accept(m_listenfd, (struct sockaddr *)&caddr, &addrlen)) == -1) return false;

		return true;
	}

	// 获取客户端ip（字符串形式）
	const string & clientip() const
	{
		return m_clientip;
	}

	// 向对端发送报文，成功返回true，失败返回false。
	bool send(const string &buffer)
	{
		if (m_clientfd == -1) 	return false;
		
		if (( ::send(m_clientfd, buffer.data(), buffer.size(), 0)) < 0) return false;
		
		return true;
	}

	// 接收对端的报文，成功返回true，失败返回false。
	// buffer-存放接收到的报文内容，maxlen-本次接收报文的最大长度。
	bool recv(string &buffer, const size_t maxlen)
	{
		buffer.clear();
		buffer.resize(maxlen);
		int readn = ::recv(m_clientfd, &buffer[0], buffer.size(), 0);
		if( readn <= 0) { buffer.clear(); return false; }
		buffer.resize(readn);
		
		return true;
	}

	// 关闭监听的socket。
	bool closelisten()
	{
		if ( m_listenfd == -1) return false;
		
		::close(m_listenfd);
		m_listenfd = -1;
		return true;
	}

	// 关闭客户端连接上来的socket。
	bool closeclient()
	{
		if (m_clientfd == -1) return false;
		
		::close(m_clientfd);
		m_clientfd = -1;
		return true;
	}

	~ctcpserver() { closelisten(); closeclient(); }
};


ctcpserver tcpserver;

void FathEXIT(int sig);	// 父进程的信号处理函数。
void ChldEXIT(int sig); // 子进程的信号处理函数。


int main(int argc, char* argv[])
{
  	if (argc != 2)
  	{
    		cout << "Using:./serverdemo1 通信端口\nExample:./serverdemo1 5005\n\n";
    		cout << "注意：运行服务端程序的Linux系统的防火墙必须开通5005端口。\n";
    		cout << "      如果是云服务器，还要开通云平台的访问策略。\n\n";
		return -1;
  	}

	// 忽略全部的信号，不希望被打扰，顺便解决了僵尸进程的问题。
	for (int ii = 1; ii <= 64; ii++) signal(ii, SIG_IGN);

	// 设置信号，在shell状态下可用“kill 进程号”或“Ctrl + c”正常终止进程。
	// 但请不要用“kill -9 + 进程号”强行终止
	signal(SIGTERM, FathEXIT); signal(SIGINT, FathEXIT); // SIGTERM 15 SIGINT 2


  	if (tcpserver.initserver(atoi(argv[1])) == false)
  	{
		perror("initserver()"); return -1;
 	}  

	while(true)
	{
		// 受理客户端的连接
		if (tcpserver.accept() == false)
		{
			perror("accept()"); return -1;
		}

		int pid = fork();
		if (pid == -1) { perror("fork()"); return -1; } // 系统资源不足
		if (pid > 0) 
		{ 
			// 父进程
			tcpserver.closeclient();	// 父进程关闭客户端连接的socket。
			continue;			// 父进程返回到循环开始的位置，继续受理客户端的连接。
		}
	
		tcpserver.closelisten(); 		// 子进程关闭监听的socket。	
		
		// 子进程需要重新设置信号。
		signal(SIGTERM, ChldEXIT);	// 子进程的退出函数与父进程不一样。
		signal(SIGINT, SIG_IGN);	// 子进程不需要捕获SIGINT信号。

		
		// 子进程负责与客户端进行通信。
		cout << "客户端已连接(" << tcpserver.clientip() << ")。\n";
		

		string buffer;
		while(true)
		{
			// 接收对端的报文
			if (tcpserver.recv(buffer, 1024) == false)
			{
				perror("recv()"); break;
			}
			cout << "接收：" << buffer << endl;

			buffer = "ok";
			if (tcpserver.send(buffer) == false)
			{
				perror("send"); break;
			}
			cout << "发送：" << buffer << endl;
		}
		
		return 0; // 子进程一定要退出，否则又会回到accept()函数的位置。
	}
}

// 父进程的信号处理函数
void FathEXIT(int sig)
{
	// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。
	signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN);
	
	cout << "父进程退出，sig = " << sig << endl;
	
	kill(0, SIGTERM);	// 向全部的子进程发送15的信号，通知它们退出。

	// 在这里增加释放资源的代码（全局的资源）。
	tcpserver.closelisten();
	
	exit(0);
}

// 子进程的信号处理函数
void ChldEXIT(int sig)
{
	// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断。
	signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN);
	
	cout << "子进程" << getpid() << "退出，sig = " << sig << endl;
	
	kill(0, SIGTERM);	// 向全部的子进程发送15的信号，通知它们退出。

	// 在这里增加释放资源的代码（全局的资源）。
	tcpserver.closeclient();
	
	exit(0);
}
